#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_log.h"
#include "tm1637.h"
#include "symbols.h"
#include <string.h>


void TM1637::_ack()
{
    // 超时数量
    const int timeout_us = 10000; // 10ms 超时
    int64_t start_time = esp_timer_get_time();

    gpio_set_direction(_pin_dta, GPIO_MODE_INPUT);
    gpio_set_level(_pin_clk, 0); // 第8这时钟结束,TM1637接下来发送ACK信号.
    _delay();

    // 可以不读发过来的ACK直接处理接下来的段落，也可以读取ACK，当读到DIO被TM1637拉低后接着处理后面的段落。
    while (gpio_get_level(_pin_dta) != 0)
    {
        // 每次循环等待添加微小延迟。
        ets_delay_us(1);

        // 超时判断
        if (esp_timer_get_time() - start_time > timeout_us)
        {
            ESP_LOGE("TM1637", "ACK 超时，硬件无响应");
            // return;
            break;
        }
    }

    gpio_set_level(_pin_clk, 1); // 第9个时钟开始，TM1637释放DIO控制权.
    _delay();
    gpio_set_level(_pin_clk, 0); // 第9个时钟结束
    _delay();
    gpio_set_direction(_pin_dta, GPIO_MODE_OUTPUT);
}

void TM1637::_send_byte(uint8_t byte)
{
    for (int i = 0; i < 8; i++)
    {
        gpio_set_level(_pin_clk, 0);
        _delay();
        gpio_set_level(_pin_dta, byte & 0x01);
        byte >>= 1;
        _delay();
        gpio_set_level(_pin_clk, 1);
        _delay();
    }
}

void TM1637::get_image(const char *str, uint8_t *image, size_t arrSize)
{
    int len = strlen(str);
    for (int i = 0; (i < arrSize) && (i < len); i++)
    {
        if (str[i] < sizeof(ascii_symbols) / sizeof(ascii_symbols[0]))
        {
            image[i] = ascii_symbols[(unsigned char)str[i]];
        }
        else // str中含有超过ascii码的字符。
        {
            image[i] = 0x00;
        }
    }
}

// public
TM1637::TM1637(gpio_num_t pin_clk, gpio_num_t pin_dta, bool four_segment)
{
    if (four_segment)
    {
        // 为了配合字符串的特点，采用从左到右，从0...5的索引。这也是大多数七段数码管模块的常用接线方法。
        int segment_idx[6] = {0, 1, 2, 3, -1, -1};
        for (int i = 0; i < (sizeof(segment_idx) / sizeof(segment_idx[0])); i++)
        {
            _segment_idx[i] = segment_idx[i];
        }
        _segment_start = 0;
        _segment_max = 4;
    }
    else
    {
        int segment_idx[6] = {0, 1, 2, 3, 4, 5}; // 待验证
        for (int i = 0; i < (sizeof(segment_idx) / sizeof(segment_idx[0])); i++)
        {
            _segment_idx[i] = segment_idx[i];
        }
        _segment_start = 0;
        _segment_max = 6;
    }

    _pin_clk = pin_clk;
    _pin_dta = pin_dta;
    // 设置默认亮度值
    _brightness = 7;

    // 初始化通讯线
    gpio_reset_pin(_pin_clk);
    gpio_reset_pin(_pin_dta);
    gpio_set_direction(_pin_clk, GPIO_MODE_OUTPUT);
    gpio_set_direction(_pin_dta, GPIO_MODE_OUTPUT);

    // 为了避免在初始化DIO期间发送一个错误的数据，把clk拉低。
    gpio_set_level(_pin_clk, 0);
    _delay();
    gpio_set_level(_pin_dta, 1);
    _delay();
    gpio_set_level(_pin_clk, 1); // ?? 为何又要拉高
    _delay();
    // 至此，TM1637准备好了通讯，clk和dta都是高电平。
}

void TM1637::init(gpio_num_t pin_clk, gpio_num_t pin_dta, bool four_segment)
{
    *this = TM1637(pin_clk, pin_dta, four_segment);
}

void TM1637::set_brightness(uint8_t level)
{
    if (level > 0x07)
    {
        level = 0x07;
    }
    _brightness = level;
}

void TM1637::set_segment_fixed(const int8_t segment_idx, const uint8_t data)
{

    if (segment_idx < 0 || segment_idx >= _segment_max)
        return;

    _start();
    _send_byte(_ADDR_FIXED); // command1:采用固定地址发送数据。
    _ack();
    _stop();
    _start();
    _send_byte(_segment_idx[segment_idx] | 0xc0); // command2: 设置地址，决定哪个数码管工作。
    _ack();
    _send_byte(data);
    _ack();
    _stop();
    _start();
    _send_byte(_brightness | 0x88); // command3:显示模式，决定亮度。
    _ack();
    _stop();
}

void TM1637::set_segment_auto(const uint8_t *data, const int data_length)
{
    _start();
    _send_byte(_ADDR_AUTO);
    _ack();
    _stop();
    _start();
    _send_byte(0xc0);
    _ack();
    for (int i = 0; i < data_length; i++)
    {
        _send_byte(data[i]);
        _ack();
    }
    _stop();
    _start();
    _send_byte(_brightness | 0x88);
    _ack();
    _stop();
}

void TM1637::disp_number_one_segment(const int8_t segment_idx, const uint8_t num, const bool dot)
{
    if (segment_idx < 0 || segment_idx > _segment_max || num > 9)
        return;

    // 获取num的ascii码
    unsigned char num_char = num + '0';
    uint8_t seg_data = ascii_symbols[num_char];

    // 处理是否显示dot
    if (dot)
    {
        seg_data |= 0x80; // 小数点是待写入的数据的最高位，也即八段数码管的bit7。
    }

    set_segment_fixed(segment_idx, seg_data);
}


void TM1637::disp_string(const char *str, bool left, int dot_idx)
{
    uint8_t ascii_data[7] = {0x00};
    if (str == NULL)
    {
        set_segment_auto(ascii_data, _segment_max);
        return;
    }

    int len = strlen(str);
    int seg_start_idx = 0; // 处理数码管时的开始索引。

    // 如果字符串长度len比数码管位数多，则都按照左对齐处理。
    if (len >= _segment_max)
    {
        left = true;
    }

    // 初始化ascii_data处理的开始索引。
    if (left)
    {
        seg_start_idx = 0;
    }
    else
    {
        seg_start_idx = _segment_max - len;
    }

    // 取得关于str的映像.
    uint8_t image[12] = {0};
    get_image(str, image, sizeof(image));

    // 如果指定了小数点位置，在置位该为的bit7
    if(dot_idx >= 0 && dot_idx < len)
    {
        image[dot_idx] |= 0x80;
    }
    
    for (int seg_idx = seg_start_idx, str_idx = 0; (seg_idx < _segment_max) && (str_idx < len); seg_idx++, str_idx++)
    {
        ascii_data[seg_idx] = image[str_idx];
    }

    // ESP_LOGD("DEBUG", "转换后的数据:0X%X-0x%X-0x%X-0x%X-0x%X-0x%X-0x%X", ascii_data[0], ascii_data[1], ascii_data[2], ascii_data[3], ascii_data[4], ascii_data[5], ascii_data[6]);
    set_segment_auto(ascii_data, _segment_max);
}

void TM1637::disp_slide(const char *str, int dot_idx)
{
    uint8_t ascii_data[7] = {0x00};
    if (str == NULL)
    {
        set_segment_auto(ascii_data, _segment_max);
        return;
    }

    int len = strlen(str);
    // 取得关于str的映像，最多可以支持12个字符的滑动显示。
    // 对于数码管显示来说，显示过长的字符串没有意义，这里主要是为了显示数字准备。
    uint8_t image[12] = {0};
    get_image(str, image, sizeof(image));

    // 如果指定了小数点位置，在置位该为的bit7
    if(dot_idx >= 0 && dot_idx < len)
    {
        image[dot_idx] |= 0x80;
    }

    if (len > sizeof(image))
        len = sizeof(image);

    for (int win_end = 0, win_start = (win_end - _segment_max + 1);
         win_start < len;
         win_end++, win_start = (win_end - _segment_max + 1))
    {
        for (int data_idx = 0, win_idx = win_start; (data_idx < _segment_max) && (win_idx <= win_end); data_idx++, win_idx++)
        {
            if (win_idx < 0 || win_idx >= len)
            {
                ascii_data[data_idx] = 0x00;
            }
            else
            {
                ascii_data[data_idx] = image[win_idx];
            }
        }
        set_segment_auto(ascii_data, _segment_max);
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void TM1637::disp_num(const int num, bool slide)
{
    char str[11] = {0x00};
    // 把num转换为字符串表示的数字.
    snprintf(str, sizeof(str), "%d", num);

    if (slide)
    {
        disp_slide(str);
    }
    else
    {
        disp_string(str, false);
    }
}

void TM1637::disp_num(const double num, bool slide)
{
    char str[11];
    // 把num转换位字符串表示的数字.
    snprintf(str, sizeof(str), "%f", num);

    if (slide)
    {
        disp_slide(str);
    }
    else
    {
        disp_string(str, false);
    }
}

void TM1637::disp_num_with_dot(const double num, bool slide)
{
    char str[12] = {0x00};
    // 把num转换为字符串表示的数字.
    snprintf(str, sizeof(str), "%f", num);  // 结果只最多保留6-7位小数。
    // 查找小数点位置
    char *ptr = strchr(str, '.');
    int dotIdx = -1;  // 小数点在字符串中的索引。
    if (ptr != NULL)
    {        
        dotIdx = ptr - str;        
        // 把小数点删除，并把之后的所有字符前移一位。
        while(*ptr != '\0')
        {
            *ptr = *(ptr + 1);
            ptr++;
        }
    }    

    // 显示数据
    if (slide)
    {
        disp_slide(str, dotIdx - 1);
    }
    else
    {
        disp_string(str, false, dotIdx - 1);
    }

}

void TM1637::disp_clear()
{
    uint8_t ascii_data[16] = {0x00};
    set_segment_auto(ascii_data, _segment_max << 1);
}
